/*
 * The MIT License
 *
 * Copyright 2014 David Rodenas Pico.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package edu.uoc.sd.fr14;

import edu.uoc.sd.fr14.comp.Bus;
import edu.uoc.sd.fr14.comp.ClockComponent;
import edu.uoc.sd.fr14.comp.Component;
import edu.uoc.sd.fr14.comp.CustomerComponent;
import edu.uoc.sd.fr14.comp.ServerComponent;
import edu.uoc.sd.fr14.comp.SocketListenerComponent;
import edu.uoc.sd.fr14.comp.SocketRemoteComponent;
import edu.uoc.sd.fr14.mesg.TargetId;
import edu.uoc.sd.fr14.model.AbstractServer;
import edu.uoc.sd.fr14.model.CustomerId;
import edu.uoc.sd.fr14.model.ServerId;

import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author drodenas
 */
public class Node {

    public static void shutdown() {
        Component.shutdown();
    }

    private ClockComponent clock;
    private final Set<Component> sockets = new HashSet<>();
    private final Map<CustomerId,CustomerComponent> customers = new HashMap<>();
    private final Map<ServerId,ServerComponent> servers = new HashMap<>();
    
    
    private final Bus bus;
    
    public Node() {
        bus = new Bus();
    }
    
    public void addComponent(Component component) {
        component.setBus(bus);
    }
    
    public void buildClock(Set<ServerId> servers, ClockConfiguration configuration) {
        clock = new ClockComponent(servers, configuration);
        clock.setBus(bus);
    }
    
    public void buildSockets(Configuration configuration) {
        
        Set<ServerId> serverIds = configuration.servers();
        Set<ServerId> mes = configuration.me();
        Set<ServerId> others = new HashSet<>(serverIds);
        others.removeAll(mes);
        
        for (ServerId other : others) {
            InetSocketAddress address = configuration.server(other);
            buildRemote(other, address);
        }
        
        for (ServerId me : mes) {
            InetSocketAddress address = configuration.server(me);
            buildListener(address.getPort());
        }
        
        InetSocketAddress customersAddress = configuration.customer();
        if (!configuration.operator()) {
            Set<CustomerId> customerIds = configuration.customers();            
            buildRemote(customerIds, customersAddress);
        } else {
            buildListener(customersAddress.getPort());
        }
    }
    
    public ServerComponent getServer(ServerId server) {
    	return servers.get(server);
    }

	public CustomerComponent getCustomer(CustomerId customer) {
		return customers.get(customer);
	}
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
	private void buildRemote(Set targets, InetSocketAddress address) {
        SocketRemoteComponent remote = new SocketRemoteComponent(targets, address);
        remote.setBus(bus);
        sockets.add(remote);
    }
    
    private void buildRemote(TargetId target, InetSocketAddress address) {
        buildRemote(Collections.singleton(target), address);
    }
    
    private void buildListener(int port) {
        SocketListenerComponent listener = new SocketListenerComponent(port);
        listener.setBus(bus);
        sockets.add(listener);
    }
    
    public void buildServers(Configuration configuration) {

        Class<AbstractServer> serverClass = configuration.serverClass();
        Set<ServerId> serverIds = configuration.servers();
        Set<ServerId> mes = configuration.me();
        
        for (ServerId id: mes) {
            buildServer(id, serverIds, serverClass);
        }
    }

    private void buildServer(ServerId id, Set<ServerId> serverIds, Class<AbstractServer> serverClass) {
        
        ServerComponent server = new ServerComponent(id, serverIds, serverClass, clock);
        server.setBus(bus);
        servers.put(id, server);
    }
    
    public void buildCustomers(Configuration configuration) {
        
        boolean operator = configuration.operator();
        if (operator) {
            Set<CustomerId> customerIds = configuration.customers();
            Set<ServerId> serverIds = configuration.servers();
            CustomerConfiguration customerConfiguration = configuration.customerConfiguration();
            
            for (CustomerId customerId : customerIds) {
                buildCustomer(customerId, serverIds, customerConfiguration);
            }
        }
    }

    private void buildCustomer(CustomerId id, Set<ServerId> serverIds, CustomerConfiguration configuration) {
        
        CustomerComponent customer = new CustomerComponent(id, serverIds, configuration);
        customer.setBus(bus);
        customers.put(id, customer);
    }

    public void stopClock() {
        clock.stop();
    }

    public void stopCustomers() {
        stop(customers.values());
    }

    public void stopSockets() {
        stop(sockets);
    }
    
    public void stopServers() {
        stop(servers.values());
    }
    
    private void stop(Collection<? extends Component> stoppables) {
        for (Component component : stoppables) {
            component.stop();
        }
    }
    
}
